package fr.generali.accueilclient.gwt.client.dispatch;

import java.util.ArrayList;
import java.util.HashMap;

import net.customware.gwt.dispatch.client.DispatchAsync;
import net.customware.gwt.dispatch.shared.Action;
import net.customware.gwt.dispatch.shared.Result;

import com.google.gwt.core.client.GWT;
import com.google.gwt.user.client.rpc.AsyncCallback;
import com.google.inject.Inject;
import com.google.inject.Singleton;

/**
 * Dispatcher which supports caching of data in memory
 * 
 * In order for caching to work, Action classes must override equals() and
 * hashCode() appropriately! Alternatively, you can pass the same instance of an
 * Action with subsequent requests (i.e., use new only once).
 * 
 * Crédit : David Chandler
 */
@Singleton
public class CachingDispatchAsync implements DispatchAsync {

	private final DispatchAsync dispatcher;
	
	private final ActionCache cache;
	
	private static HashMap<Action<Result>, ArrayList<AsyncCallback<Result>>> pendingCallbacks = new HashMap<Action<Result>, ArrayList<AsyncCallback<Result>>>();

	@Inject
	public CachingDispatchAsync(final DispatchAsync dispatcher, ActionCache cache) {
		this.dispatcher = dispatcher;
		this.cache = cache;
	}

	public <A extends Action<R>, R extends Result> void execute(final A action, final AsyncCallback<R> callback) {
		if (action instanceof Cacheable) {
			executeWithCache(action, callback);
		} else {
			dispatcher.execute(action, callback);
		}
	}
	
	
	@SuppressWarnings("unchecked")
	private <A extends Action<R>, R extends Result> void executeWithCache(final A action, final AsyncCallback<R> callback) {
		
		GWT.log("Executing with cache " + action.toString());
		
		final ArrayList<AsyncCallback<Result>> pending = pendingCallbacks.get(action);
		// TODO need a timeout here?
		
		if (pending != null) {
			GWT.log("Command pending for " + action, null);
			// Add to pending commands for this action
			pending.add((AsyncCallback<Result>) callback);
			return;
		}
		
		R result = cache.get(action);

		if (result != null) {
			
			GWT.log("PutsIntoCache hit for " + action, null);
			callback.onSuccess(result);
			
		} else {
			GWT.log("Calling real service for " + action, null);
			
			pendingCallbacks.put((Action<Result>) action, new ArrayList<AsyncCallback<Result>>());
			
			dispatcher.execute(action, new AsyncCallback<R>() {

				public void onSuccess(R result) {
					GWT.log("Real service returned successfully " + action, null);
					// Process all pending callbacks for this action
					ArrayList<AsyncCallback<Result>> callbacks = pendingCallbacks.remove(action);
					for (AsyncCallback<Result> pendingCallback : callbacks) {
						pendingCallback.onSuccess(result);
					}
					cache.put(action, result);
					callback.onSuccess(result);
				}				
				
				public void onFailure(Throwable caught) {
					// Process all pending callbacks for this action
					ArrayList<AsyncCallback<Result>> callbacks = pendingCallbacks.remove(action);
					for (AsyncCallback<Result> pendingCallback : callbacks) {
						pendingCallback.onFailure(caught);
					}
					callback.onFailure(caught);
				}
			});
		}
	}

}
